/** 简单文件读写类
 * @file file.hpp
 * @author Kiven Lee
 * @version 1.0
 * @date 2021-09-14
 *
 * @copyright Kivensoft (c) 2018 - 2021
 *
 */

#include <cstdio>

namespace klib {
    class file_t {

    private:
        FILE *_fp;

    public:
        /** 文件访问位置移动模式 */
        enum class seek_t { begin, cureent, end };

        // 禁止拷贝构造与拷贝赋值
        file_t(const file_t&) = delete;
        file_t& operator=(const file_t&) = delete;

        file_t() : _fp(nullptr) {}

        file_t(FILE *fp) : _fp(fp) {}

        /** 移动构造函数 */
        file_t(file_t&& src): _fp(src._fp) { src._fp = NULL; }

        file_t(const char *filename, const char *mode) {
            _fp = std::fopen(filename, mode);
        }

        ~file_t() { if (_fp) std::fclose(_fp); }

        /** 移动赋值函数 */
        file_t& operator=(file_t&& src) {
            if (_fp) std::fclose(_fp);
            _fp = src._fp;
            src._fp = nullptr;
            return *this;
        }

        /** 隐式类型转换赋值 */
        file_t& operator=(FILE* fp) {
            if (_fp) std::fclose(_fp);
            _fp = fp;
            return *this;
        }

        /** 分离并返回文件指针，避免对象离开作用域后自动关闭文件 */
        FILE* detach() {
            FILE *r = _fp;
            _fp	= nullptr;
            return r;
        }

        /** 返回文件是否已经打开 */
        bool is_opened() const { return _fp != nullptr; }

        bool operator!() const { return !is_opened(); }

        FILE* get() const { return _fp; }

        /** 打开文件
         *
         * @param filename 文件名
         * @param mode 打开模式，参见fopen的说明
         * @return true: 打开成功, false: 打开失败
         */
        bool open(const char *filename, const char *mode) {
            if (_fp) std::fclose(_fp);
            _fp = std::fopen(filename, mode);
            return is_opened();
        }

        /** 关闭文件 */
        void close() {
            if (_fp) {
                std::fclose(_fp);
                _fp = nullptr;
            }
        }

        /** 获取文件长度，注意，该函数将破坏当前的文件读写位置，需要的话用户自己进行保存和恢复文件位置 */
        long length() {
            std::fseek(_fp, 0, (int)seek_t::end);
            return std::ftell(_fp);
        }

        /** 返回文件当前读写位置 */
        long pos() const { return std::ftell(_fp); }

        /** 移动文件读写位置 */
        bool seek(long offset, seek_t mode) { return !std::fseek(_fp, offset, (int)mode); }

        bool seek_begin(long offset) { return seek(offset, seek_t::begin); }
        bool seek_current(long offset) { return seek(offset, seek_t::cureent); }
        bool seek_end(long offset) { return seek(offset, seek_t::end); }

        /** 文件读取
         *
         * @param dst 读取内容写入的位置
         * @param size 计划读取长度（字节为单位）
         * @return size_t 实际读取长度
         */
        size_t read(void *dst, size_t size) { return std::fread(dst, 1, size, _fp); }

        /** 文件写入
         *
         * @param buf 写入内容的地址
         * @param size 计划写入长度（字节为单位）
         * @return size_t 实际写入长度
         */
        size_t write(void *buf, size_t size) { return std::fwrite(buf, 1, size, _fp); }

        /** 文件读取，当实际读取长度不等于期望长度时，返回false */
        bool read_strict(void *dst, size_t size) { return read(dst, size) == size; }

        /** 文件写入，当实际写入长度不等于期望长度时，返回false */
        bool write_strict(void *buf, size_t size) { return write(buf, size) == size; }
    };

    namespace path {
        /** 获取去除最后一级的目录或文件名得到的路径
         *
         * @param path
         * @return std::string_view
         */
        std::string_view base_path(const std::string_view& path) {
            const char *pb = path.data(), *p = pb + path.length() - 1;
            while (p >= pb && *p != '/' && *p != '\\') --p;
            size_t len = p > pb ? p - pb : p == pb ? 1 : 0;
            return std::string_view(pb, len);
        }

        /** 获取最后一级的目录或文件名
         *
         * @param path
         * @return std::string_view
         */
        std::string_view base_file(const std::string_view& path) {
            const char *pb = path.data(), *pe = pb + path.length(), *p = pe - 1;
            while (p >= pb && *p != '/' && *p != '\\') --p;
            if (p < pb) p = pb; else ++p;
            return std::string_view(p, pe - p);
        }

        /** 获取文件扩展名
         *
         * @param file
         * @return std::string_view
         */
        std::string_view file_ext(const std::string_view& file) {
            const char *pb = file.data(), *pe = pb + file.length(), *p = pe - 1;
            while (p >= pb && *p != '.') --p;
            if (p < pb) p = pe; else ++p;
            return std::string_view(p, pe - p);
        }

    } // namespace path

} // namespace klib
